﻿using System;
using System.Xml.Linq;
using System.Data;
using System.IO;
using System.Linq;
using System.Collections.Generic;

namespace Evpo.ContactData.Data
{
    public class XmlDocumentGenerator
    {
        public XmlDocumentGenerator(DataTable table)
        {
            _table = table;
        }

        #region Consts
        private const string DateTimeStyle = "s1";
        private const string PositiveString = "yes";
        private const string NegativeString = "no";
        #endregion

        #region Fields

        private readonly DataTable _table;
        private XNamespace DefaultNs = XNamespace.Get("urn:schemas-microsoft-com:office:spreadsheet");
        private XNamespace SSNamespace = XNamespace.Get("urn:schemas-microsoft-com:office:spreadsheet:special");

        #endregion

        #region Properties
        #endregion

        #region Methods

        private XElement CreateCell(int index, string type, string text)
        {
            return new XElement(DefaultNs + "Cell",
                new XAttribute(XNamespace.Xmlns + "ss", SSNamespace),
                new XAttribute(SSNamespace + "Index", index.ToString()),
                (type == ExcelDataTypes.DateTime) ? new XAttribute(SSNamespace + "StyleID", DateTimeStyle) : null,
                new XElement(DefaultNs + "Data",
                    new XAttribute(SSNamespace + "Type", type),
                    text)
                );
        }

        private void RemoveSSNamespace(XElement elem)
        {
            if (elem.Attribute(XNamespace.Xmlns + "ss") != null)
                elem.Attribute(XNamespace.Xmlns + "ss").Remove();
        }

        public void Generate(Stream stream)
        {
            XElement root = XElement.Parse(Resources.Template);
            XElement table = root.Descendants(DefaultNs + "Table").Single();

            XElement rowElem = new XElement(DefaultNs + "Row");
            table.Add(rowElem);

            for (int i = 0; i < _table.Columns.Count; i++)
            {
                XElement cell = CreateCell(i + 1, ExcelDataTypes.String, _table.Columns[i].ColumnName);
                rowElem.Add(cell);
                RemoveSSNamespace(cell);
            }

            foreach (DataRow row in _table.Rows)
            {
                rowElem = new XElement(DefaultNs + "Row");
                table.Add(rowElem);
                for (int i = 0; i < _table.Columns.Count; i++)
                {
                    if (row[i] == DBNull.Value)
                    {
                        continue;
                    }

                    Type type = _table.Columns[i].DataType;
                    ExcelItem item = GetExcellItem(_table.Columns[i].DataType, row[i]);
                    XElement cell = CreateCell(i + 1, item.TypeName, item.Value);
                    rowElem.Add(cell);
                    RemoveSSNamespace(cell);
                }
            }

            using (TextWriter writer = new NoCloseStreamWriter(stream))
            {
                root.Save(writer, SaveOptions.None);
            }
        }

        private class NoCloseStreamWriter : StreamWriter
        {
            public NoCloseStreamWriter(Stream stream) : base(stream) { }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(false);
            }
        }

        private ExcelItem GetExcellItem(Type type, object value)
        {
            ExcelItem retVal = new ExcelItem();

            if (type == typeof(Boolean))
            {
                retVal.TypeName = ExcelDataTypes.String;
                retVal.Value = ((bool)value) ? PositiveString : NegativeString;
            }
            else if ((new List<Type>() { typeof(int), typeof(double), typeof(decimal) }).Contains(type))
            {
                retVal.TypeName = ExcelDataTypes.Number;
                retVal.Value = value.ToString();
            }
            else if (type == typeof(DateTime))
            {
                retVal.TypeName = ExcelDataTypes.DateTime;
                retVal.Value = ((DateTime)value).ToString("o");
            }
            else
            {
                retVal.TypeName = ExcelDataTypes.String;
                retVal.Value = value.ToString();
            }

            return retVal;
        }

        #endregion Methods

        #region Classes
        private class ExcelDataTypes
        {
            internal const string String = "String";
            internal const string Number = "Number";
            internal const string DateTime = "DateTime";
        }

        private class ExcelItem
        {
            internal string TypeName
            {
                get;
                set;
            }

            internal string Value
            {
                get;
                set;
            }
        }
        #endregion Classes
    }
}
